package session

import (
	"mi/compiler/ast"
	"mi/compiler/binder"
	"mi/compiler/checker"
)

func getSymbolKind(typeChecker checker.TypeChecker, symbol *binder.Symbol, location ast.Node) ScriptElementKind {
	var flags = symbol.Flags
	switch{
	case flags &binder.Class != 0:
		return SEKClassElement
	case flags &binder.Enum != 0:
		return SEKEnumElement
	case flags &binder.Interface != 0:
		return SEKInterfaceElement
	case flags &binder.TypeParameter != 0:
		return SEKTypeParameterElement
	case flags&binder.TypeAlias != 0:
		return SEKClassElement
	}

	var result = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, flags, location)
	if result == SEKUnknown {
		switch {
		case flags &binder.TypeParameter != 0:
			return SEKTypeParameterElement
		case flags &binder.EnumMember != 0:
			return SEKVariableElement
		}
	}

	return result
}

func getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker checker.TypeChecker, symbol *binder.Symbol, flags binder.SymbolFlags, location ast.Node) ScriptElementKind {
	//if typeChecker.IsUndefinedSymbol(symbol) {
	//	return SEKVariableElement
	//}
	//if location.Kind == syntaxkind.ThisKeyword{
	//	return SEKParameterElement
	//}
	if flags &binder.Variable != 0 {
		return SEKVariableElement
	}

	if flags&binder.FieldLike != 0 {
		return SEKMemberVariableElement
		//switch symbol.ValueDeclaration.(type) {
		//case *ast.FieldDeclaration:
		//	return SEKMemberVariableElement
		//case *ast.MethodDeclaration:
		//	return SEKMemberFunctionElement
		//}
	}

	switch{
	case flags&binder.Function != 0:
		return SEKFunctionElement
	case flags&binder.MethodLike != 0:
		return SEKMemberFunctionElement
	case flags&binder.Constructor != 0:
		return SEKConstructorImplementationElement

	}

	return SEKUnknown
}


type SemanticMeaning int

const (
	smNone SemanticMeaning= 0x0
	smValue SemanticMeaning= 0x1
	smType SemanticMeaning= 0x2
	smNamespace SemanticMeaning= 0x4
	smAll = smValue | smType | smNamespace
)

type symbolDisplayParts struct {
	displayParts []SymbolDisplayPart
	documentation []SymbolDisplayPart
	symbolKind string
	tags []JSDocTagInfo
}

//
//// TODO(drosen): Currently completion entry details passes the SemanticMeaning.All instead of using semanticMeaning of location
//func getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker types.TypeChecker, symbol *types.Symbol,
//	sourceFile *types.SourceFile, enclosingDeclaration *types.Node, location *types.Node, semanticMeaning SemanticMeaning) *symbolDisplayParts {
//
//	var displayParts []SymbolDisplayPart
//	var documentation []SymbolDisplayPart
//	var tags []JSDocTagInfo
//	var symbolFlags = symbol.Flags
//	var symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, symbolFlags, location)
//	var hasAddedSymbolInfo bool
//	var isThisExpression = location.Kind == types.SKThisKeyword && util.IsExpression(location)
//	var type_ *types.Type
//
//	var addNewLineIfDisplayPartsExist = func() {
//		if len(displayParts) > 0 {
//			displayParts = append(displayParts, lineBreakPart())
//		}
//	}
//
//	var addOfPrefix = func (){
//		displayParts = append(displayParts, spacePart())
//		displayParts = append(displayParts, keywordPart(types.SKOfKeyword))
//		displayParts = append(displayParts, spacePart())
//	}
//
//	var addFullSymbolName = func (symbol *types.Symbol, enclosingDeclaration *types.Node) {
//		var fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbol, enclosingDeclaration || sourceFile, /*meaning*/ undefined,
//			SymbolFormatFlags.WriteTypeParametersOrArguments|SymbolFormatFlags.UseOnlyExternalAliasing);
//		addRange(displayParts, fullSymbolDisplayParts);
//	}
//
//	var addPrefixForAnyFunctionOrVar = func (symbol *types.Symbol, symbolKind string) {
//		addNewLineIfDisplayPartsExist()
//		if len(symbolKind) > 0 {
//			pushTypePart(symbolKind)
//			displayParts.push(spacePart())
//			addFullSymbolName(symbol)
//		}
//	}
//
//	var pushTypePart = func (symbolKind string) {
//		switch symbolKind {
//		case SEKVariableElement,
//			SEKFunctionElement,
//			SEKConstructorImplementationElement:
//			displayParts.push(textOrKeywordPart(symbolKind));
//			return
//		default:
//			displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
//			displayParts.push(textOrKeywordPart(symbolKind));
//			displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
//			return
//		}
//	}
//
//	var addSignatureDisplayParts = func (signature *types.Signature, allSignatures []*types.Signature, flags types.TypeFormatFlags) {
//		addRange(displayParts, signatureToDisplayParts(typeChecker, signature, enclosingDeclaration, flags|TypeFormatFlags.WriteTypeArgumentsOfSignature));
//		if (allSignatures.length > 1) {
//			displayParts.push(spacePart());
//			displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
//			displayParts.push(operatorPart(SyntaxKind.PlusToken));
//			displayParts.push(displayPart((allSignatures.length - 1).toString(), SymbolDisplayPartKind.numericLiteral));
//			displayParts.push(spacePart());
//			displayParts.push(textPart(allSignatures.length == = 2 ? "overload" : "overloads"));
//			displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
//		}
//		documentation = signature.getDocumentationComment();
//		tags = signature.getJsDocTags();
//	}
//
//	var writeTypeParametersOfSymbol = func(symbol *types.Symbol, enclosingDeclaration *types.Node) {
//		const typeParameterParts = mapToDisplayParts(writer = > {
//		typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplayFromSymbol(symbol, writer, enclosingDeclaration);
//		});
//		addRange(displayParts, typeParameterParts);
//	}
//
//
//	// Class at constructor site need to be shown as constructor apart from property,method, vars
//	if symbolKind != SEKUnknown || symbolFlags&ast.SFClass != 0 /* || symbolFlags & ast.SFAlias != 0 */ {
//
//		var signature *types.Signature
//		if isThisExpression {
//			type_ = typeChecker.getTypeOfNode(location)
//		} else {
//			type_ = typeChecker.getTypeOfSymbolAtLocation(symbol, location)
//		}
//
//		if type_ != nil {
//			if location.Parent != nil && location.Parent.Kind == types.SKPropertyAccessExpression {
//				var right = location.Parent.ToPropertyAccessExpression().Name
//				// Either the location is on the right of a property access, or on the left and the right is missing
//
//				if right.ToNode() == location || (right != nil && util.GetFullWidth(right.ToNode()) == 0) {
//					location = location.Parent
//				}
//			}
//
//			// try get the call/construct signature from the type if it matches
//			var callExpression *types.Node /*  CallExpression | NewExpression */
//			if location.Kind == types.SKCallExpression || location.Kind == types.SKNewExpression {
//				callExpression = location
//			} else if isCallExpressionTarget(location) || isNewExpressionTarget(location) {
//				callExpression = location.Parent
//			}
//
//			if callExpression != nil {
//				var candidateSignatures []*types.Signature
//				signature, candidateSignatures := typeChecker.GetResolvedSignature(callExpression)
//				if signature == nil && len(candidateSignatures) > 0 {
//					// Use the first candidate:
//					signature = candidateSignatures[0]
//				}
//
//				var useConstructSignatures = callExpression.Kind == types.SKNewExpression ||
//					callExpression.ToCallLikeExpression().Expr.Kind == types.SKSuperKeyword
//
//				var allSignatures []*types.Signature
//				if useConstructSignatures {
//					allSignatures = typeChecker.GetSignaturesOfType(type_, types.SIKConstruct)
//				} else {
//					allSignatures = typeChecker.GetSignaturesOfType(type_, types.SIKCall)
//				}
//
//				if !core.ContainsSignature(allSignatures, signature.Target) && !core.ContainsSignature(allSignatures, signature) {
//					// Get the first signature if there is one -- allSignatures may contain
//					// either the original signature or its target, so check for either
//					if len(allSignatures) > 0 {
//						signature = allSignatures[0]
//					} else {
//						signature = nil
//					}
//				}
//
//				if signature != nil {
//					if useConstructSignatures && symbolFlags&ast.SFClass != 0 {
//						// Constructor
//						symbolKind = SEKConstructorImplementationElement
//						addPrefixForAnyFunctionOrVar(type_.Symbol, symbolKind)
//					} else {
//						addPrefixForAnyFunctionOrVar(symbol, symbolKind)
//					}
//					//
//					//else if (symbolFlags & SymbolFlags.Alias) {
//					//	symbolKind = ScriptElementKind.alias;
//					//	pushTypePart(symbolKind);
//					//	displayParts.push(spacePart());
//					//	if (useConstructSignatures) {
//					//		displayParts.push(keywordPart(SyntaxKind.NewKeyword));
//					//		displayParts.push(spacePart());
//					//	}
//					//	addFullSymbolName(symbol);
//					//}
//
//					switch symbolKind {
//					case SEKMemberVariableElement,
//						SEKVariableElement,
//						SEKParameterElement,
//						SEKLocalVariableElement:
//						// If it is call or construct signature of lambda's write type name
//						displayParts = append(displayParts, punctuationPart(types.SKColonToken))
//						displayParts = append(displayParts, spacePart())
//						if useConstructSignatures {
//							displayParts = append(displayParts, keywordPart(types.SKNewKeyword))
//							displayParts = append(displayParts, spacePart())
//						}
//
//						if !(type_.Flags&types.TFAnonymous != 0 && type_.Symbol != nil) {
//							addRange(displayParts, symbolToDisplayParts(typeChecker,
//							type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments));
//						}
//						addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature);
//
//					default:
//						// Just signature
//						addSignatureDisplayParts(signature, allSignatures, types.TFFNone)
//					}
//					hasAddedSymbolInfo = true
//				}
//			} else if isNameOfFunctionDeclaration(location) || // name of function declaration
//				location.Kind == types.SKConstructorKeyword && location.Parent.Kind == types.SKConstructorDeclaration { // At constructor keyword of constructor declaration
//				// get the signature from the declaration and write it
//				var functionDeclaration = location.Parent.ToFunctionLikeDeclaration()
//				var allSignatures []*types.Signature
//				if functionDeclaration.Kind == types.SKConstructorDeclaration {
//					allSignatures = typeChecker.GetSignaturesOfType(type_, types.SIKConstruct)
//				} else {
//					allSignatures = typeChecker.GetSignaturesOfType(type_, types.SIKCall)
//				}
//
//				//if (!typeChecker.isImplementationOfOverload(functionDeclaration)) {
//				//	signature = typeChecker.getSignatureFromDeclaration(functionDeclaration);
//				//} else {
//				//	signature = allSignatures[0]
//				//}
//
//				if functionDeclaration.Kind == types.SKConstructorDeclaration {
//					// show (constructor) Type(...) signature
//					symbolKind = SEKConstructorImplementationElement
//					addPrefixForAnyFunctionOrVar(type_.Symbol, symbolKind)
//				} else {
//					// (function/method) symbol(..signature)
//					addPrefixForAnyFunctionOrVar(functionDeclaration.kind == = SyntaxKind.CallSignature &&
//						!(
//					type.symbol.flags&SymbolFlags.TypeLiteral ||
//					type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol:
//					symbol, symbolKind);
//				}
//
//				addSignatureDisplayParts(signature, allSignatures)
//				hasAddedSymbolInfo = true
//			}
//		}
//	}
//	if symbolFlags&ast.SFClassLike != 0 && !hasAddedSymbolInfo && !isThisExpression {
//		if util.GetDeclarationOfKind(symbol, types.SKClassExpression) != nil {
//			// Special case for class expressions because we would like to indicate that
//			// the class name is local to the class body (similar to function expression)
//			//      (local class) class <className>
//			// pushTypePart(ScriptElementKind.localClassElement)
//		} else {
//			// Class declaration has name which is not local.
//			displayParts = append(displayParts, keywordPart(types.SKClassKeyword))
//		}
//		displayParts = append(displayParts, spacePart())
//		addFullSymbolName(symbol)
//		writeTypeParametersOfSymbol(symbol, sourceFile)
//	}
//	if symbolFlags&ast.SFInterfaceLike != 0 && semanticMeaning & smType != 0 {
//		addNewLineIfDisplayPartsExist()
//		displayParts = append(displayParts, keywordPart(types.SKInterfaceKeyword))
//		displayParts = append(displayParts,spacePart())
//		addFullSymbolName(symbol)
//		writeTypeParametersOfSymbol(symbol, sourceFile)
//	}
//	//if (symbolFlags & SymbolFlags.Enum) {
//	//	addNewLineIfDisplayPartsExist();
//	//	if (forEach(symbol.declarations, isConstEnumDeclaration)) {
//	//		displayParts.push(keywordPart(SyntaxKind.ConstKeyword));
//	//		displayParts.push(spacePart());
//	//	}
//	//	displayParts.push(keywordPart(SyntaxKind.EnumKeyword));
//	//	displayParts.push(spacePart());
//	//	addFullSymbolName(symbol);
//	//}
//	//if (symbolFlags & SymbolFlags.Module) {
//	//	addNewLineIfDisplayPartsExist();
//	//	const declaration = <ModuleDeclaration > getDeclarationOfKind(symbol, SyntaxKind.ModuleDeclaration);
//	//	const isNamespace = declaration && declaration.name && declaration.name.kind == = SyntaxKind.Identifier;
//	//	displayParts.push(keywordPart(isNamespace ? SyntaxKind.NamespaceKeyword : SyntaxKind.ModuleKeyword));
//	//	displayParts.push(spacePart());
//	//	addFullSymbolName(symbol);
//	//}
//	if symbolFlags&ast.SFTypeParameter != 0 && semanticMeaning&smType != 0 {
//		addNewLineIfDisplayPartsExist()
//		displayParts = append(displayParts, punctuationPart(types.SKOpenParenToken))
//		displayParts = append(displayParts, textPart("type parameter"))
//		displayParts = append(displayParts, punctuationPart(types.SKCloseParenToken))
//		displayParts = append(displayParts, spacePart())
//		addFullSymbolName(symbol, nil)
//		if symbol.Parent != nil {
//			// Class/Interface type parameter
//			addOfPrefix()
//			addFullSymbolName(symbol.Parent, enclosingDeclaration)
//			writeTypeParametersOfSymbol(symbol.Parent, enclosingDeclaration)
//		} else {
//			// Method/function type parameter
//			var declaration =  util.GetDeclarationOfKind(symbol, types.SKTypeParameter).ToNode()
//			debug.Assert(declaration != nil)
//			declaration = declaration.Parent
//
//			if declaration != nil {
//				if isFunctionLikeKind(declaration.Kind) {
//					addOfPrefix()
//					var signature = typeChecker.getSignatureFromDeclaration( < SignatureDeclaration > declaration);
//					if (declaration.kind == = SyntaxKind.ConstructSignature) {
//						displayParts.push(keywordPart(SyntaxKind.NewKeyword));
//						displayParts.push(spacePart());
//					} else if (declaration.kind != = SyntaxKind.CallSignature && ( < SignatureDeclaration > declaration).name) {
//						addFullSymbolName(declaration.symbol);
//					}
//					addRange(displayParts, signatureToDisplayParts(typeChecker, signature, sourceFile, TypeFormatFlags.WriteTypeArgumentsOfSignature));
//				}
//			}
//		}
//	}
//	//if symbolFlags & ast.SFEnumMember != 0 {
//	//	addPrefixForAnyFunctionOrVar(symbol, "enum member");
//	//	const declaration = symbol.declarations[0];
//	//	if (declaration.kind == = SyntaxKind.EnumMember) {
//	//		const constantValue = typeChecker.getConstantValue( < EnumMember > declaration);
//	//		if (constantValue != = undefined) {
//	//			displayParts.push(spacePart());
//	//			displayParts.push(operatorPart(SyntaxKind.EqualsToken));
//	//			displayParts.push(spacePart());
//	//			displayParts.push(displayPart(constantValue.toString(), SymbolDisplayPartKind.numericLiteral));
//	//		}
//	//	}
//	//}
//	//if (symbolFlags & SymbolFlags.Alias) {
//	//	addNewLineIfDisplayPartsExist();
//	//	if (symbol.declarations[0].kind == = SyntaxKind.NamespaceExportDeclaration) {
//	//		displayParts.push(keywordPart(SyntaxKind.ExportKeyword));
//	//		displayParts.push(spacePart());
//	//		displayParts.push(keywordPart(SyntaxKind.NamespaceKeyword));
//	//	}
//	//	else {
//	//		displayParts.push(keywordPart(SyntaxKind.ImportKeyword));
//	//	}
//	//	displayParts.push(spacePart());
//	//	addFullSymbolName(symbol);
//	//	ts.forEach(symbol.declarations, declaration => {
//	//		if (declaration.kind == = SyntaxKind.ImportEqualsDeclaration) {
//	//			const importEqualsDeclaration = <ImportEqualsDeclaration > declaration;
//	//			if (isExternalModuleImportEqualsDeclaration(importEqualsDeclaration)) {
//	//				displayParts.push(spacePart());
//	//				displayParts.push(operatorPart(SyntaxKind.EqualsToken));
//	//				displayParts.push(spacePart());
//	//				displayParts.push(keywordPart(SyntaxKind.RequireKeyword));
//	//				displayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
//	//				displayParts.push(displayPart(getTextOfNode(getExternalModuleImportEqualsDeclarationExpression(importEqualsDeclaration)), SymbolDisplayPartKind.stringLiteral));
//	//				displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
//	//			} else {
//	//				const internalAliasSymbol = typeChecker.getSymbolAtLocation(importEqualsDeclaration.moduleReference);
//	//				if (internalAliasSymbol) {
//	//					displayParts.push(spacePart());
//	//					displayParts.push(operatorPart(SyntaxKind.EqualsToken));
//	//					displayParts.push(spacePart());
//	//					addFullSymbolName(internalAliasSymbol, enclosingDeclaration);
//	//				}
//	//			}
//	//			return true;
//	//		}
//	//	});
//	//}
//	if !hasAddedSymbolInfo {
//		if symbolKind != SEKUnknown {
//			if type_ != nil {
//				if isThisExpression {
//					addNewLineIfDisplayPartsExist();
//					displayParts = append(displayParts, keywordPart(types.SKThisKeyword))
//				} else {
//					addPrefixForAnyFunctionOrVar(symbol, symbolKind)
//				}
//
//				// For properties, variables and local vars: show the type
//				if symbolKind == SEKMemberVariableElement ||
//					symbolFlags&ast.SFVariable != 0 ||
//					symbolKind == SEKLocalVariableElement ||
//					isThisExpression {
//					displayParts = append(displayParts, punctuationPart(types.SKColonToken))
//					displayParts = append(displayParts, spacePart())
//					// If the type is type parameter, format it specially
//					if type_.Symbol != nil && type_.Symbol.Flags&ast.SFTypeParameter != 0 {
//						var typeParameterParts = mapToDisplayParts(writer = > {
//						typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(<TypeParameter>type, writer, enclosingDeclaration);
//						});
//						addRange(displayParts, typeParameterParts);
//					} else {
//						addRange(displayParts, typeToDisplayParts(typeChecker,
//						type, enclosingDeclaration));
//					}
//				} else if symbolFlags&ast.SFFunction != 0 ||
//					symbolFlags&ast.SFMethod != 0 ||
//					symbolFlags&ast.SFConstructor != 0 ||
//					symbolFlags&ast.SFSignature != 0 ||
//					symbolKind == SEKMemberFunctionElement {
//					var allSignatures = typeChecker.GetSignaturesOfType(type_, types.SIKCall)
//					addSignatureDisplayParts(allSignatures[0], allSignatures, types.TFFNone)
//				}
//			}
//		} else {
//			symbolKind = getSymbolKind(typeChecker, symbol, location)
//		}
//	}
//
//	//if documentation == nil {
//	//	documentation = symbol.getDocumentationComment();
//	//	tags = symbol.getJsDocTags();
//	//	if (documentation.length == = 0 && symbol.flags&SymbolFlags.Property) {
//	//		// For some special property access expressions like `experts.foo = foo` or `module.exports.foo = foo`
//	//		// there documentation comments might be attached to the right hand side symbol of their declarations.
//	//		// The pattern of such special property access is that the parent symbol is the symbol of the file.
//	//		if (symbol.parent && forEach(symbol.parent.declarations, declaration => declaration.kind == = SyntaxKind.SourceFile)) {
//	//		for (const declaration of symbol.declarations) {
//	//		if (!declaration.parent || declaration.parent.kind != = SyntaxKind.BinaryExpression) {
//	//		continue;
//	//		}
//	//
//	//		const rhsSymbol = typeChecker.getSymbolAtLocation((<BinaryExpression>declaration.parent).right);
//	//		if (!rhsSymbol) {
//	//		continue;
//	//		}
//	//
//	//		documentation = rhsSymbol.getDocumentationComment();
//	//		tags = rhsSymbol.getJsDocTags();
//	//		if (documentation.length > 0) {
//	//		break;
//	//		}
//	//		}
//	//		}
//	//	}
//	//}
//
//	return &symbolDisplayParts{
//		displayParts:  displayParts,
//		documentation: documentation,
//		symbolKind:    symbolKind,
//		tags:          tags,
//	}
//}